using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using TagTool.Devices;

namespace TagTool.Devices.Midi
{
    #region Midi message handling

    /// <summary> Delegate for midi callback function </summary>
    /// <param name="midiHandle"> Midi device handle </param>
    /// <param name="message"> Midi message </param>
    /// <param name="instance"> Instance parameter (ignored with callbacks) </param>
    /// <param name="param1"> High data </param>
    /// <param name="param2"> Low data </param>
    /// <returns></returns>
    [UnmanagedFunctionPointer(CallingConvention.Winapi)]
    public delegate MMResult MidiInProc(IntPtr midiHandle, uint message, uint instance, uint param1, uint param2);

    /// <summary> Delegate for all channel voice messages </summary>
    public delegate void ChannelVoiceEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for NoteOff message </summary>
    public delegate void NoteOffEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for NoteOn message </summary>
    public delegate void NoteOnEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for PolyphonicPressure message </summary>
    public delegate void PolyphonicPressureEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for ControlChange message </summary>
    public delegate void ControlChangeEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for ProgramChange message </summary>
    public delegate void ProgramChangeEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for midi message </summary>
    public delegate void ChannelPressureEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for PitchWheel message </summary>
    public delegate void PitchWheelEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for all ModeControl messages </summary>
    public delegate void ModeControlEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for LocalControl message </summary>
    public delegate void LocalControlEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for AllNotesOff message </summary>
    public delegate void AllNotesOffEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for OmniModeOff message </summary>
    public delegate void OmniModeOffEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for OmniModeOn message </summary>
    public delegate void OmniModeOnEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for MonophonicMode message </summary>
    public delegate void MonophonicModeEventHandler(object sender, MidiDeviceEventArgs e);
    /// <summary> Delegate for PolyphonicMode message </summary>
    public delegate void PolyphonicModeEventHandler(object sender, MidiDeviceEventArgs e);

    // TODO: Implement midi system message handlers

    #endregion

    public class MidiInputDevice : MidiDevice
    {
		private MidiInputCaps capabilities;
		
        private MidiInProc proc;
        /// <summary> Channel voice messages </summary>
        public event ChannelVoiceEventHandler ChannelVoiceEvent;
        public event NoteOffEventHandler NoteOffEvent;
        public event NoteOnEventHandler NoteOnEvent;
        public event PolyphonicPressureEventHandler PolyphonicPressureEvent;
        public event ProgramChangeEventHandler ProgramChangeEvent;
        public event ControlChangeEventHandler ControlChangeEvent;
        public event ChannelPressureEventHandler ChannelPressureEvent;
        public event PitchWheelEventHandler PitchWheelEvent;
        /// <summary> Channel mode control messages </summary>
        public event ModeControlEventHandler ModeControlEvent;

        // TODO: Declare midi system message handlers
        // TODO: Find out which on which midi input device to receive channel voice messages

        public MidiInputDevice(uint deviceId)
        	: base(deviceId)
        {
            type = MidiDeviceType.Input;
            proc = new MidiInProc(OnMidiIn);
        }

        ~MidiInputDevice()
        {
        	Disconnect();
        }

        #region Properties


        public MidiInputCaps Capabilities
        {
        	get { return capabilities; }
        }
        
        #endregion
        
        public override void Connect()
        {
            if(deviceHandle != IntPtr.Zero)
            {
                Disconnect();
            }
            deviceHandle = new IntPtr();
            MMResult result = WinMidi.MidiInOpen(ref deviceHandle, deviceId, proc, 0, (uint)CallbackFlag.Function);
            if (result == MMResult.NoError)
            {
            	connected = true;
                result = WinMidi.MidiInStart(deviceHandle);
            }
        }

        public override void Disconnect()
        {
            if (deviceHandle != IntPtr.Zero)
            {
                MMResult result = WinMidi.MidiInClose(deviceHandle);
                if (result == MMResult.NoError)
                {
                    deviceHandle = IntPtr.Zero;
                    connected = false;
                }
            }
        }
 
        public override MMResult GetDevCaps()
        {
            capabilities = new MidiInputCaps();
            uint cbMidiInCaps = (uint)Marshal.SizeOf(capabilities);
            MMResult result = WinMidi.MidiInGetDevCaps(deviceId, ref capabilities, cbMidiInCaps);
            return result;
        }
 
        public MMResult OnMidiIn(IntPtr hMidiIn, uint wMsg, uint dwInstance, uint dwParam1, uint dwParam2)
        {
            
            uint status = MidiStandard.GetMessageStatus(dwParam1);
            switch (wMsg)
            {
                case (uint)MidiMessage.InputOpen:
                    break;
                case (uint)MidiMessage.InputClose:
                    break;
                case (uint)MidiMessage.InputData:
                    break;
                case (uint)MidiMessage.InputLongData:
                    break;
                case (uint)MidiMessage.InputError:
                    break;
                case (uint)MidiMessage.InputLongError:
                    break;
                case (uint)MidiMessage.InputMoreData:
                    break;
            }
            switch (status)
            {
                case (uint)MidiMessageStatus.NoteOff:
                    if (ChannelVoiceEvent != null)
                    {
                        ChannelVoiceEvent(this, new MidiDeviceEventArgs(new ChannelVoiceMessage(dwParam1, dwParam2)));
                    }
                    if (NoteOffEvent != null)
                    {
                        NoteOffEvent(this, new MidiDeviceEventArgs(new NoteOffMessage(dwParam1, dwParam2)));
                    }
                    break;
                case (uint)MidiMessageStatus.NoteOn:
                    // If velocity is zero then this really is a NoteOff
                    if (((dwParam1 >> 16) & (uint)MidiMessageMask.Data) == 0)
                    {
                        if (ChannelVoiceEvent != null)
                        {
                            ChannelVoiceEvent(this, new MidiDeviceEventArgs(new ChannelVoiceMessage(dwParam1, dwParam2)));
                        }
                        if (NoteOffEvent != null)
                        {
                            NoteOffEvent(this, new MidiDeviceEventArgs(new NoteOffMessage(dwParam1, dwParam2)));
                        }
                    }
                    else
                    {
                        if (ChannelVoiceEvent != null)
                        {
                            ChannelVoiceEvent(this, new MidiDeviceEventArgs(new ChannelVoiceMessage(dwParam1, dwParam2)));
                        }
                        if (NoteOnEvent != null)
                        {
                            NoteOnEvent(this, new MidiDeviceEventArgs(new NoteOnMessage(dwParam1, dwParam2)));
                        }
                    }
                    break;
                case (uint)MidiMessageStatus.PolyphonicKeyPressure:
                    if (PolyphonicPressureEvent != null)
                    {
                        PolyphonicPressureEvent(this, new MidiDeviceEventArgs(new PolyphonicPressureMessage(dwParam1, dwParam2)));
                    }
                    break;
                case (uint)MidiMessageStatus.ControlChange:
                    // Decide if message is control change or channel mode
                    if (((dwParam1 >> 8) & (uint)MidiMessageMask.Data) >= 122)
                    {
                        if (ModeControlEvent != null)
                        {
                            ModeControlEvent(this, new MidiDeviceEventArgs(new ModeControlMessage(dwParam1, dwParam2)));
                        }
                    }
                    else
                    {
                        if (ControlChangeEvent != null)
                        {
                            ControlChangeEvent(this, new MidiDeviceEventArgs(new ControlChangeMessage(dwParam1, dwParam2)));
                        }
                    }
                    break;
                case (uint)MidiMessageStatus.ProgramChange:
                    if (ProgramChangeEvent != null)
                    {
                        ProgramChangeEvent(this, new MidiDeviceEventArgs(new ProgramChangeMessage(dwParam1, dwParam2)));
                    }
                    break;
                case (uint)MidiMessageStatus.ChannelPressure:
                    if (ChannelPressureEvent != null)
                    {
                        ChannelPressureEvent(this, new MidiDeviceEventArgs(new ChannelPressureMessage(dwParam1, dwParam2)));
                    }
                    break;
                case (uint)MidiMessageStatus.PitchWheelChange:
                    if (PitchWheelEvent != null)
                    {
                        PitchWheelEvent(this, new MidiDeviceEventArgs(new PitchWheelMessage(dwParam1, dwParam2)));
                    }
                    break;
            }

            return MMResult.NoError;
        }
    }
}
